package org.marcelo.agro.core;


import org.marcelo.agro.core.Helper;
import java.util.Iterator;
import java.util.List;
import org.hibernate.*;
import org.hibernate.Session;
import org.hibernate.criterion.*;
import org.hibernate.transform.Transformers;
import org.marcelo.agro.entidades.noadministrados.Estadisticas;

/**
 * Clase para utilidades de acceso a la BBDD mediante metodos genericos
 * que manejan la mayoria de los casos posibles
 *
 * Los metodos de busqueda proveen acceso paginado y no paginado, filtrado o no
 * La API provee una interfaz para ordenar opcionalmente el resultado por una cantidad
 * N de ordenes posibles
 * 
 * Todas las acciones a la base de datos deben arrojar errores, para que las capas superiores sean capaces de capturarlos
 * y manejarlos, esta capa debe arrojar errores
 *
 * @author camilojd
 * @author marcelof
 */
public class GenericManager {

    /**
     * Obtener un objeto de la base de datos a partir de su clase y su id
     *
     * @param clazz el literal de clase para el objeto pedido
     * @param id el identificador del objeto
     * @return una instancia del objeto recuperada desde la BBDD
     */
    public static <T> T getObjectById(Class<T> clazz, int id) throws Exception{
        Session session = Helper.currentSession();
        
        try {
            Transaction t = session.beginTransaction();
            Criteria criteria = session.createCriteria(clazz);
            criteria.add(Restrictions.eq("id", id));
            List<T> results = criteria.list();
            t.commit();
            if (results.isEmpty()) {
                return null;
            }
            return results.get(0);
        }
        catch (HibernateException he) {
            close();
            throw new Exception(he);
        }
    }

    /**
     * Grabar un objeto nuevo en la BBDD
     *
     * @param object un objeto no almacenado previamente
     */
    public static void saveObject(Object object) throws Exception{
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            session.persist(object);
            tx.commit();
        }
        catch(HibernateException he) {
            tx.rollback();
            close();
            throw new Exception(he);
        }
    }

    /**
     * Actualizar los campos de un objeto en la BBDD
     *
     * @param object un objeto previamente almacenado, a actualizar
     * @return una instancia persistida del mismo objeto
     */
    public static Object updateObject(Object object) throws Exception{
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            Object mergedObject = session.merge(object);
            tx.commit();
            return mergedObject;
        }
        catch(HibernateException he) {
            tx.rollback();
            close();
            throw new Exception(he);
        }
    }

    /**
     * Borrar un objeto en base a su clase y id
     * Se asume que dicho objeto no posee FK que referencia al mismo en otras tablas, pero se
     * debe verificar el resultado de la operacion para ver si surgio algun tipo de error
     *
     * @param clazz la clase del objeto a borrar
     * @param id el identificador del objeto
     * @return la respuesta del motor a la accion
     */
    public static Boolean deleteObjectById(Class clazz, int id) throws Exception{
        Boolean result = Boolean.TRUE;
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            Query query = session.createQuery("DELETE FROM " + clazz.getSimpleName() + " o WHERE o.id = :id");
            query.setParameter("id", id);
            print(query);
            tx.commit();
            return result;
        }
        catch(HibernateException he) {
            tx.rollback();
            close();
            throw new Exception(he);
        }
    }

    /**
     * Listar todos los objetos de una clase dada, ordenando de manera opcional
     * por una o mas propiedades
     *
     * @param clazz la clase del objeto a traer
     * @param orders una lista de N ordenes posibles al hacer el select
     * @return una lista de los objetos
     */
    public static <T> List<T> getAllObjects(Class<T> clazz, Order... orders) throws Exception{
//        HibernateUtil.getSessionFactory().close();
        Session session = Helper.currentSession();
        Transaction t = session.beginTransaction();
        try{
            Criteria criteria = session.createCriteria(clazz);
            for (Order order : orders) {
                criteria.addOrder(order);
            }
            List<T> result = criteria.list();
            t.commit();
            return result;
        }
        catch(HibernateException he) {
            close();
            throw new Exception(he);
        }
    }
    
    /**
     * Listar todos los objetos de una clase dada de forma ascendente
     * @param clazz la clase del objeto a traer
     * @return una lista de los objetos
     */
    public static <T> List<T> getAllObjectsAsc(Class<T> clazz) throws Exception{
        Session session = Helper.currentSession();
        Transaction t= session.beginTransaction();
        try{    
            Criteria criteria = session.createCriteria(clazz);
            criteria.addOrder(Order.asc("id"));
            List<T> retorno = criteria.list();
            t.commit();
            return retorno;
        }
        catch(HibernateException he) {
            throw new Exception(he);
        }
    }
    
    /**
     * Listar todos los objetos de una clase dada de forma descendente
     * @param clazz la clase del objeto a traer
     * @return una lista de los objetos
     */
    public static <T> List<T> getAllObjectsDesc(Class<T> clazz) throws Exception{
//        HibernateUtil.getSessionFactory().close();
        Session session = Helper.currentSession();
        Transaction t = session.beginTransaction();
        try{
            Criteria criteria = session.createCriteria(clazz);
            criteria.addOrder(Order.desc("id"));
            List<T> retorno = criteria.list();
            t.commit();
            return retorno;
        }
        catch(HibernateException he) {
            close();
            throw new Exception(he);
        }
    }

    /**
     * Listar todos los objetos de una clase dada, ordenando de manera opcional
     * por una o mas propiedades, y paginando dicho resultado
     *
     * @param clazz la clase del objeto a traer
     * @param perPage la cantidad de objetos por pagina
     * @param pageNumber el numero de pagina
     * @param orders los criterios de ordenes deseados, si los hubiera
     * @return un objeto paginador correspondiente a la sublista generada
     */
//    public static <T> Page<T> getAllObjects(Class<T> clazz, int perPage, int pageNumber, Order... orders) {
//        List<T> resultList = getAllObjects(clazz, orders);
//        Paginator<T> paginator = new Paginator<T>(resultList, perPage);
//
//        return paginator.page(Integer.valueOf(pageNumber).toString());
//    }

    /**
     * Listar todos los objetos de una clase dada, aplicando una lista de filtros
     * especificados, ordenando de manera opcional por una o mas propiedades
     *
     * @param clazz la clase del objeto a traer
     * @param criterions la lista de criterios especificados
     * @param orders los criterios de ordenes deseados, si los hubiera
     * @return una lista de objetos filtrada
     */
    public static <T> List<T> getFilteredObjects(Class<T> clazz, List criterions, Order... orders) throws Exception{
//        HibernateUtil.getSessionFactory().close();
        Session session = Helper.currentSession();
        Transaction t = session.beginTransaction();
        try{
            Criteria criteria = session.createCriteria(clazz);
            for (Iterator it = criterions.iterator(); it.hasNext();) {
                Criterion c = (Criterion) it.next();
                criteria.add(c);
            }
            for (Order order : orders) {
                criteria.addOrder(order);
            }

            List<T> result = criteria.list();
            t.commit();
            return  result;
        }
        catch(HibernateException he) {
            close();
            throw new Exception(he);
        }
    }

    /**
     * Listar todos los objetos de una clase dada, aplicando una lista de filtros
     * especificados, ordenando de manera opcional por una o mas propiedades, y paginando el resultado

     * @param clazz la clase del objeto a traer
     * @param criterions la lista de criterios especificados
     * @param perPage la cantidad de objetos por pagina
     * @param pageNumber el numero de pagina
     * @param orders los criterios de ordenes deseados, si los hubiera
     * @return un objeto paginador correspondiente a la sublista generada
     */
//    public static <T> Page<T> getFilteredObjects(Class<T> clazz, List criterions, int perPage, int pageNumber,
//                                                 Order... orders) {
//        List<T> resultList = getFilteredObjects(clazz, criterions, orders);
//
//        return createPageFromList(resultList, perPage, pageNumber);
//    }

    /**
     * Buscar en la base de datos, utilizando un objeto POJO cualquiera conocido por Hibernate
     * para crear un criterio dinamico. Se puede aplicar un orden al resultado si asi se desea
     *
     * @param object un POJO cualquiera conocido por Hibernate, para ser utilizado como filtro
     * @param orders los criterios de ordenes deseados, si los hubiera
     * @return una lista de objetos en la cuales se busca
     */
    public static <T>List<T> searchObjectsLike(T t, Order... orders) throws Exception{
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            Criteria criteria = session.createCriteria(t.getClass());
            criteria.add(Example.create(t).ignoreCase().excludeZeroes().enableLike(MatchMode.ANYWHERE));
            for (Order order : orders) {
                criteria.addOrder(order);
            }
            List<T> result = criteria.list();
            tx.commit();
            return result;
        }
        catch(HibernateException he){
            close();
            throw  new Exception(he);
                    
        }
    }

    /**
     * Buscar en la base de datos, utilizando un objeto POJO cualquiera conocido por Hibernate
     * para crear un criterio dinamico, y luego paginar los resultados obtenidos.
     * Se puede aplicar un orden al resultado si asi se desea
     *
     * @param object un POJO cualquiera conocido por Hibernate, para ser utilizado como filtro
     * @param perPage la cantidad de objetos por pagina
     * @param pageNumber el numero de pagina
     * @param orders los criterios de ordenes deseados, si los hubiera
     * @return un objeto paginador correspondiente a la sublista generada
     */
//    public static Page searchObjectsLike(Object object, int perPage, int pageNumber, Order... orders) {
//        List resultList = searchObjectsLike(object, orders);
//
//        return createPageFromList(resultList, perPage, pageNumber);
//    }

    /**
     * Metodo auxiliar para crear un paginador
     *
     * @param list una lista de objetos cualquiera
     * @param perPage la cantidad de objetos por pagina
     * @param pageNumber el numero de pagina
     * @return una pagina conteniendo una sublista de objetos
     */
//    private static <T> Page<T> createPageFromList(List<T> list, int perPage, int pageNumber) {
//        Paginator<T> paginator = new Paginator<T>(list, perPage);
//
//        return paginator.page(Integer.valueOf(pageNumber).toString());
//    }

    
    /**
     * Metodo que obtiene la cantidad de objetos en base a un criterio. Es utilizado en ArticuloController. PABLO
     * @param criterio de busqueda. Ejemplo: "From Articulo art where art.articulo.id = " + articuloOld.getArticulo().getId()
     * @return int retorna la cantidad de objetos
     */
    public static int getCountObjets(String criterio) throws Exception{
        Query query = null;
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            query = session.createQuery(criterio);
            tx.commit();

            if(query.list().isEmpty())
                return 0;
            else
                return query.list().size();
        }
        catch(HibernateException he){
            close();
            throw new Exception(he);
        }
    }

    /**
     *
     * @param Padre
     * @param hijo
     */
//    public static DatabaseOperationResult BorrarTodosLosHijos(Class padre , Object hijo){
//
//        DatabaseOperationResult.ErrorType errorType = null;
//        int recordsAffected = 0;
//        RuntimeException exception = null;
//        try {
//            Session session = Helper.currentSession();();
//            Transaction tx = session.beginTransaction();
//            Query query = session.createQuery("DELETE FROM " + padre.getSimpleName() + " padre WHERE padre."+hijo.getClass().getSimpleName().toString().toLowerCase()+" = :hijo");
//            query.setParameter("hijo", hijo);
//            System.out.print(query.toString());
//            recordsAffected = query.executeUpdate();
//            tx.commit();
//            session.close();
//        } catch (RuntimeException e) {
//            if (e instanceof ConstraintViolationException) {
//                errorType = DatabaseOperationResult.ErrorType.CONSTRAINT_VIOLATION;
//                System.out.print("Error1: "+ e.getMessage());
//            } else {
//                errorType = DatabaseOperationResult.ErrorType.OTHER;
//                System.out.print("Error2: " + e.getMessage());
//            }
//            exception = e;
//        }
//
//        return new DatabaseOperationResult(errorType, recordsAffected, exception);
//
//     }

    /**
     * Metodo que realiza una consulta, y retorna la lista de tuplas.
     * ejemplo: From Articulo art where art.articulo.id = null and art.articulosEstado.id=1 order by art.id
     * @param <T>
     * @param criterio
     * @return
     */
    public static <T> List<T> getListEnBaseACriterio(String criterio) throws Exception{
        List<T> result ;
        Query query ;
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            query = session.createQuery(criterio);
            result = query.list();
            tx.commit();
            return result;
        }
        catch(HibernateException he){
            close();
            throw new Exception(he);
        }
    }
    
    public static void saveOrUpdate(Object object) throws Exception{
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            session.saveOrUpdate(object);
            tx.commit();
        }
        catch(HibernateException he) {
            tx.rollback();
            close();
            throw new Exception(he);
        }
    }
    
    public static void saveOrUpdates(Object[] objects) throws Exception{
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            for (Object object : objects) {
                session.saveOrUpdate(object);
            }    
            tx.commit();
        }
        catch(HibernateException he){
            tx.rollback();
            close();
            throw new Exception(he);
        }
    }
    
    public <T> List<T> getFileteredObject(String hqlQuery) throws Exception{
        
        Session session = Helper.currentSession();;
        Transaction tx = session.beginTransaction();
        try{
            Query q = session.createQuery(hqlQuery);
            List<T> result =  q.list();
            tx.commit();  
            return result; 
        }
        catch(HibernateException he) {
            close();
            throw new Exception(he);
        }
    }
    
    /**
     * Metodo para borrar un objeto de la base de datos, si encuentra un error lanza una excepcion
     * @param obj
     * @throws Exception 
     */
    public static void delete(Object obj) throws Exception{
        Session session = Helper.currentSession();
        Transaction tx ;
        tx = session.beginTransaction();
        try {
            session.delete(obj);
            tx.commit();
        }
        catch(HibernateException he) {
            tx.rollback();
            close();
            throw new Exception(he);
        }
        
    }
    
    /**
     * La idea de este metodo es traer una proyeccion de una tabla de la base de datos en\
     * base a una lista de valores, Se debe pasar el clazz y la lista de Campos A Proyectar
     * @param clazz La Clase del Objeto a Proyectar
     * @param proyection La lista de Campos a Proyecta en un ProjectionList
     * @return Puede retornar null en caso que no encuentre ningun resultado
     * Caso contrario retornara la lista de resultados
     */
    public static <T0,T1>  List<T0> getProjectionListObjects(Class<T1> clazz, Projection proyection) throws Exception {
        Session session = Helper.currentSession();
        List<T0> result ;
        Transaction tx;
        
        tx = session.beginTransaction();
        Criteria criteria = session.createCriteria(clazz).setProjection(proyection);
        criteria.setResultTransformer(Transformers.aliasToBean(Estadisticas.class));
        result = criteria.list();
        tx.commit();
        
        return result;
    }
    
    /**
     * La idea de este metodo es traer una proyeccion de una tabla de la base de datos en\
     * base a una lista de valores, Se debe pasar el clazz y la lista de Campos A Proyectar
     * tambien una lista de restricciones
     * @param clazz La Clase a Proyectar
     * @param proyection La lista de Proyeccionas del Objeto en un Objeto ProjectionList
     * @param restrictions Una lista de Restricciones
     * 
     * @return Puede retornar null en caso que no encuentre ningun resultado
     * Caso contrario retornara la lista de resultados
     */
    public static <T0,T1>  List<T0> getProjectionListObject(Class clazz, Projection proyection, List restrictions) throws Exception {
        Session session = Helper.currentSession();
        List<T0> result = null;
        Transaction tx;
        
        tx = session.beginTransaction();
        Criteria criteria = session.createCriteria(clazz).setProjection(proyection);
        criteria.setResultTransformer(Transformers.aliasToBean(Estadisticas.class));
        Iterator it = restrictions.iterator();
        while (it.hasNext()) {
            criteria.add( (Criterion) it.next() );
        }
        result = criteria.list();
        tx.commit();
        
        
        return result;
    }
    
    /**
     * Metodo para realizar una proyeccion con una serie de restricciones que se le pasa como argumento
     * El Objeto projection tiene la lista de campos qu se desea consultar
     * Solo para motivos de prueba ahora uso la clase Estadisticas.class
     * pero la idea es que la entidad no administrada se pueda defimir en capaz superiores
     * Ahora agregue la funcionalidad para que en capaz superiores puedan definir el objeto no administrado
     * el unico problema es que retorna un object, hay que hacer un cast
     */
    public  static <T> T getProjectionObject(Class objetoNoAdministrado,Class objetoAProyectar, Projection projection, List restricciones) throws Exception{
    
        Session session = Helper.currentSession();
        T result = null;
        Transaction tx;
        tx = session.beginTransaction();
        Criteria criteria = session.createCriteria(objetoAProyectar).setProjection(projection);
        Iterator it = restricciones.iterator();
        while (it.hasNext()) {
            criteria.add( (Criterion) it.next() );
        }
        criteria.setResultTransformer(Transformers.aliasToBean(objetoNoAdministrado));
        result = (T) criteria.uniqueResult();
        print(criteria);
        tx.commit();
        
        return result
;        
        
    }
    
    /**
     * Metodo para ejecutar comandos de SQL desde el GenericManager
     * La idea es permitir hacer un Update, Delete o Insert o Procedimientos almacenados
     * @param sqlCommand un String que representa la consulta SQL
     */
    public static void executeSql(String sqlCommand, String[] values) throws Exception{
        Session session = Helper.currentSession();
        
        Transaction tx = session.beginTransaction();
        try {
            SQLQuery sqlQuery = session.createSQLQuery(sqlCommand);
            for (int i=0 ; i< values.length ;i++) {
                sqlQuery.setParameter(i, values[i]);
            }
            sqlQuery.executeUpdate();
            print(sqlQuery);
            tx.commit();   
        }
        catch(HibernateException he) {
            tx.rollback();
            close();
            throw new Exception(he);
        }
        
    }
    
    /**
     * Segun esto puedo pasar una lista de Hijos de cualquier tipo, y un padre, realizara el borrado de 
     * todos los hijos y luego del padre
     * @param parent
     * @param childs 
     */
    public  static void deleteParentAndChild(Object parent, Object[]... childs) throws Exception{
        
        Session session = Helper.currentSession();
        Transaction tx = session.beginTransaction();
        try {
            for (Object[] obj : childs) {
                for (int i = 0 ; i< obj.length; i++){
                    session.delete(obj[i]);
                }
            }
            session.delete(parent);
            tx.commit();   
        }
        catch(HibernateException he) {
            tx.rollback();
            close();
            throw new Exception(he);
        }
        
    }
    
    public static void refreshObject(Object object) throws Exception{
        Session session = Helper.currentSession();
        try {
            Transaction tx = session.beginTransaction();
            session.refresh(object);
            tx.commit();
        }
        catch(HibernateException e) {
            close();
            throw new Exception(e);
            
        }
    }
    
//    public static Boolean delete(Object object) throws Exception{
//        Boolean result = Boolean.FALSE;
//        Session session = Helper.currentSession();
//        try {
//            Transaction tx = session.beginTransaction();
//            session.delete(object);
//            tx.commit();
//            result = Boolean.TRUE;
//        }
//        catch(HibernateException e) {
//            close();
//            throw new Exception(e);    
//        }
//        return result;
//    }
    
    public static void close() {
        Helper.currentSession().close();
    }
    
    private static void print(Object toPirnt){
        System.out.println("======================================");
        System.out.println(toPirnt.toString());
        System.out.println("======================================");
    }
 
}
